/*
 * StateMonitoringVCR.java
 *
 * Created on December 1, 2005, 4:17 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.mbari.vcr;

import java.util.Timer;
import java.util.TimerTask;
import org.mbari.util.NumberUtil;

/**
 * This class is a replacement fo the SonyVCRProxy. It is greatly simplified and
 * does much more. It monitors timecode as well as state automatically. In record
 * mode it also writes timecode to the userbits on the tape.
 *
 * TODO 20051202 brian: This class needs to be tested.
 * @author brian
 */
public class StateMonitoringVCR extends MonitoringVCR {
    
    
    /**
	 * @uml.property  name="stopObserver"
	 * @uml.associationEnd  multiplicity="(1 1)" inverse="this$0:org.mbari.vcr.StateMonitoringVCR$StopObserver"
	 */
    private final StopObserver stopObserver = new StopObserver();
    /**
	 * @uml.property  name="recordObserver"
	 * @uml.associationEnd  multiplicity="(1 1)" inverse="this$0:org.mbari.vcr.StateMonitoringVCR$RecordObserver"
	 */
    private final RecordObserver recordObserver = new RecordObserver();
    /**
	 * @uml.property  name="statusObserver"
	 * @uml.associationEnd  multiplicity="(1 1)" inverse="this$0:org.mbari.vcr.StateMonitoringVCR$StatusObserver"
	 */
    private final StatusObserver statusObserver = new StatusObserver();
    
    /**
     * Creates a new instance of StateMonitoringVCR
     */
    public StateMonitoringVCR() {
        this(new VCRAdapter());
    }
    
    public StateMonitoringVCR(IVCR vcr) {
        super(vcr);
        addStateObserver(stopObserver);
        addStateObserver(recordObserver);
        addStateObserver(statusObserver);
    }
    
    
    /**
     * Returns the interval between timecode requests
     * @return The time between timecode requests in milliseconds
     */
    public long getTimecodeInterval() {
        return stopObserver.getInterval();
    }
    
    /**
     * Sets the interval between requests for timecodes from the VCR. Miminum
     * allowed value is 33.
     *
     * @param timecodeInterval Interval in millisecs. Default is 40
     */
    public void setTimecodeInterval(long timecodeInterval) {
        stopObserver.setInterval(timecodeInterval);
    }
    
    public long getUserbitsInterval() {
        return recordObserver.getInterval();
    }
    
    /**
     * Sets the interval between writing the time to userbits on the VCR tape.
     * Miminum allowed value is 250. Note the time resolution is only to a second
     *
     * @param userbitsInterval Interval in millisecs. Default is 1000
     */
    public void setUserbitsInterval(long userbitsInterval) {
        recordObserver.setInterval(userbitsInterval);
    }
    
    public long getStatusInterval() {
        return statusObserver.getInterval();
    }
    
    /**
     * Sets the interval between request for status from the VCR. By default,
     * the VCR asks for status after every operation sent through the IVCR
     * interface. However, sometimes the user will change the state of the VCR
     * by operating the VCR directly, not through software.
     *
     * @param statusInterval Interval in millisecs. Default is 2500. Minimum is
     *  250.
     */
    public void setStatusInterval(long statusInterval) {
        statusObserver.setInterval(statusInterval);
    }
    
    
    /**
     * This class listens to the state of the VCR. If it is stopped it stops
     * the requests for timecodes to the VCR. When the VCR tape is in motion
     * it sends requests for updated timecodes.
     */
    private class StatusObserver extends StateObserver {
        
        StatusObserver() {
            super();
            intervalMin = 250;
            long INTERVAL_DEFAULT = 1000;
            setInterval(INTERVAL_DEFAULT);
            startTimer();
            
        }
        
        public void update(Object obj, Object changeCode) {
            IVCRState vcrState = (IVCRState) obj;
            if (vcrState.isConnected()) {
                startTimer();
            } else {
                stopTimer();
            }
        }
        
        void startTimer() {
            if (getTimer() == null) {
                setTimer(new Timer(true));
                getTimer().schedule(new ReadStateTimerTask(), 0, getInterval());
            }
        }
        
        /**
         * This is used to monitor the timecode during VCR operations
         */
        private class ReadStateTimerTask extends TimerTask {
            public void run() {
                requestStatus();
            }
        }
    }
    
    
    /**
     * This class listens to the state of the VCR. If it is stopped it stops
     * the requests for timecodes to the VCR. When the VCR tape is in motion
     * it sends requests for updated timecodes.
     */
    private class StopObserver extends StateObserver {
        
        
        
        StopObserver() {
            super();
            intervalMin = 33;
            long INTERVAL_DEFAULT = 50;
            setInterval(INTERVAL_DEFAULT);
        }
        
        
        /**
         * We shouldn't need to override StateObserver.update. However, on the
         * ships we're using a bridge between the Comm port and navproc which
         * always returns a 0 status
         */
        public void update(Object obj, Object changeCode) {
            IVCRState vcrState = (IVCRState) obj;
            if (vcrState != null) {
                startTimer();
            } else {
                stopTimer();
            }
        }
        
        void startTimer() {
            if (getTimer() == null) {
                setTimer(new Timer(true));
                getTimer().schedule(new ReadTimecodeTimerTask(), 0, getInterval());
            }
        }
        
        /**
         * This is used to monitor the timecode during VCR operations
         */
        private class ReadTimecodeTimerTask extends TimerTask {
            public void run() {
                /*
                 * LTimeCode is on the audio track. It's slightly less accurate than
                 * the VTimeCode, however, the VTimeCode can't be read except when
                 * the VCR is playing. So use the LTimeCode for everything but
                 * play mode.
                 */
                if (getVcrState().isPlaying()) {
                    requestVTimeCode();
                } else {
                    requestLTimeCode();
                }
            }
        }
    }
    
    
    /**
     * This class listens for when the VCR is recording. When it is recording
     * it writes time in seconds to the userbits on the tape.
     */
    private class RecordObserver extends StateObserver {
        
        RecordObserver() {
            super();
            intervalMin = 250;
            long INTERVAL_DEFAULT = 500;
            setInterval(INTERVAL_DEFAULT);
        }
        
        public void update(Object obj, Object changeCode) {
            IVCRState state = (IVCRState) obj;
            if (!state.isRecording()) {
                stopTimer();
            } else {
                startTimer();
            }
        }
        
        void startTimer() {
            if (getTimer() == null) {
                setTimer(new Timer(true));
                getTimer().schedule(new WriteUserbitsTimerTask(), 0, getInterval());
            }
        }
        
        /**
         * This is used to write UTC time to the userbits when recording
         */
        private class WriteUserbitsTimerTask extends TimerTask {
            public void run() {
                int timeSeconds = (int) (System.currentTimeMillis() / 1000L);
                byte[] b = NumberUtil.toByteArray(timeSeconds, true);
                presetUserbits(b);
            }
        }
    }
    
}
